using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;


/*
	Classes to make threading and interaction with a UI easier.
	
	The goal is to have a thread proc that can feed state and prgress back to the UI
	and respond to an abort request.
	There are two threading patterns supported:
	1. A long process that happens in steps. The UI reports the steps somehow.
	2. A long process without steps. All that the main program
	   requires is a note of the success or failure of the process.
*/
namespace WinFormThreading
{
    // class to pass state between interface and thread proc
    public class ThreadProcessState
    {	
		
		// process states
		// ready / working / finished / aborted
		public enum state {ready, working, done, cancelled, errored}
		state m_state = state.ready;
		
		// progress
        int  m_totalSteps = 1;     // default one step
        int  m_currentStep = 0;    // 
        object m_userData = null;  // user data
        Exception m_Exception; 
        
        public ThreadProcessState() { }
        
        public int TotalSteps { get {return m_totalSteps;} set {m_totalSteps = value;} }
        public int CurrentStep { get {return m_currentStep;} set {m_currentStep = value;} }
		public state State { get {return m_state;}}
        public bool IsDone { get {return (m_state == state.done);} }
		public bool IsCancelled { get { return (m_state == state.cancelled); } }
		public bool IsErrored { get { return (m_state == state.errored); } }
		public bool IsAborted { get { return (m_state == state.cancelled || m_state == state.errored); } }
		public bool IsWorking { get { return (m_state == state.working); } }
		public object UserData { get { return m_userData; } set { m_userData = value; } }
		public Exception Exception { get { return m_Exception; } set { m_Exception = value; } }

        // called as the thread is kicked off
        public void onStartWork()
        {
            m_currentStep = 0;
            m_state = state.ready;
        }
		// interface for setting state
        public void cancel() { m_state = state.cancelled; }  // called mainly the UI
		public void start() { m_state = state.working; }  // called by the thread class
		public void finish() { m_state = state.done; }    // called by the thread class
		public void setError(Exception ex)
		{
			m_Exception = ex;
			if (m_state != state.cancelled) // don't change state if already cancelled
				m_state = state.errored; 
		}
    }

    // declare the callback function signature
    // users of the class below pass in a function with this pattern
    delegate void ProgressStateDelegate(ThreadProcessState processState);

    /*
		wraps the nitty-gritty of a background task with feedback
	*/
	public class WorkerBase 
	{
		// the members below are passed in the constructor by the user

		// Usually a form or a winform control that implements "Invoke/BeginInvode"
		ContainerControl m_sender = null;
		// The delegate method (callback) on the sender to call
		Delegate m_senderDelegate = null;
		// the object to hold state information
		ThreadProcessState m_processState;
		
		// Constructor used by caller using ThreadPool (untested)
		public WorkerBase()	{}
		
		// Constructor called by calle using ThreadPool OR ThreadStart
		// see above for params
		public WorkerBase(ContainerControl sender, Delegate senderDelegate, ThreadProcessState state)
		{
			m_sender = sender;
			m_senderDelegate = senderDelegate;
			m_processState = state;
        }
		
		// called to start the work
		public void KickOffThread()
		{
			Thread t = new Thread(new ThreadStart(this.RunProcess));
			t.IsBackground = true;    //make them a daemon - prevent thread callback issues
			t.Start();
		}

		// Method for ThreadPool QueueWorkerItem (untested)
		public void RunProcess(object obj)
		{
			Thread.CurrentThread.IsBackground = true; //make them a daemon
			object[] objArray = (object[]) obj;
			m_sender = (System.Windows.Forms.Form) objArray[0];
			m_senderDelegate = (System.Delegate) objArray[1];
            m_processState = (ThreadProcessState)objArray[2];

			LocalRunProcess();
		}

		// Method for ThreadStart delegate
		public void RunProcess()
		{
			Thread.CurrentThread.IsBackground = true; //make them a daemon
            m_processState.onStartWork();
			LocalRunProcess();
		}
		
		/// <summary>
		/// Local Method for the actual work.
		/// </summary>
		protected virtual void LocalRunProcess()
		{
			m_processState.start();
            // do nothing implementation that just loops
            for (int step = 0; step < m_processState.TotalSteps && 
                               !m_processState.IsAborted; ++step)
			{
			    m_processState.CurrentStep = step;
				DoWorkStep(m_processState);
				// state is either working or aborted.
				// note: the completing step will not callback 
				// to avoid sending two callbacks with the done state.
				if (step < m_processState.TotalSteps-1 && 
				    m_processState.State == ThreadProcessState.state.working)
					Callback();
			}
            // send the done call back unless already aborted
			if (!m_processState.IsAborted)
			{
				m_processState.finish();
           }
			Callback();
		}
		// Overridable by the inheriting class to specialise the work done
		protected virtual void DoWorkStep(ThreadProcessState processState)
		{
			// default is a small delay - only good for testing progress lines.
			Thread.Sleep(200);
		}

		// invoked during and on completion 
		protected void Callback()
		{
			// may have to snapshot thread state to avoid > 1 done message
			m_sender.BeginInvoke(m_senderDelegate, new object[] { m_processState });
		}


	}

}

